<?php
namespace App\Libs;
//封装单例模式（三私一公）
use App\Modules\Admin\Services\AdminUsersService;
use App\Modules\Diagnose\Models\VideoCall;
use App\Modules\Diagnose\Services\GraphicService;
use App\Modules\Diagnose\Services\OrderDiagnoseService;
use App\Modules\Diagnose\Services\VideoCallService;
use App\Modules\Doctor\Services\DoctorService;
use App\Modules\User\Services\UserService;
use Illuminate\Support\Facades\Log;

class SwooleWebSocket
{
    private static $instance;       //存放当前对象属性
    private static $server;         //存放webSocket对象属性

    //private $dir = storage_path('swoole.log');

    private function __construct() {
        //创建webSocket对象
        self::$server = new \Swoole\WebSocket\Server('0.0.0.0', 8877,SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
        //设置参数
        self::$server->set([
            'daemonize'     => 1,               // 作为守护进程运行，需同时设置log_file
            'log_file'      => storage_path('swoole.log'),//$this->dir,      // 指定标准输出和错误日志文件
            //配置SSL证书和密钥路径
            'ssl_cert_file' => "/etc/letsencrypt/live/ysts.yax.com.cn/fullchain.pem",
            'ssl_key_file'  => "/etc/letsencrypt/live/ysts.yax.com.cn/privkey.pem"
        ]);
        //注册事件
        self::$server->on('open',[$this,'onOpen']);           //将当前类中的onOpen方法作为open的事件处理函数
        self::$server->on('message',[$this,'onMessage']);
        self::$server->on('close',[$this,'onClose']);
    }

    private function __clone() {
        // TODO: Implement __clone() method.
        trigger_error('Clone is not allowed !');
    }

    public static function getInstance() {
        if ( !(self::$instance instanceof self) ){
            self::$instance = new self();
        }
        return self::$instance;
    }

    //当前客户端连接之后要执行的方法
    public function onOpen($server,$request) {
        /*echo "client: {$request->fd}\n";
        $server->push($request->fd, "hello, welcome\n");*/
        //$server->push($request->fd,['code'=>0,'msg'=>'']);
    }

    //当客户端向服务器发送消息时要执行的方法
    public function onMessage($server,$frame) {
        //处理业务逻辑
        $data = json_decode($frame->data,true);
        Log::info($frame->data);
        Log::info($data);
        if($data==false) {
            return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1000,'msg'=>'请求数据格式非法']));
            //echo json_encode(['action'=>'tips','tips_type'=>'bind','code'=>1000,'msg'=>'请求数据格式非法']);
        }
        if(self::validate_req_data($data)==false) {
            //echo json_encode(['action'=>'tips','tips_type'=>'bind','code'=>1001,'msg'=>'请求参数有误']);
           return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1001,'msg'=>'请求参数有误']));
        }
        $sid = array_get($data,'sid');
        $uid = \Hashids::decode(array_get($data,'sid'))[0];
        \Log::info('uid:'.$uid);
        if(empty($uid)) {
            //echo json_encode(['action'=>'tips','code'=>1001,'msg'=>'请求参数有误']);
            return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1001,'msg'=>'请求参数有误']));
        }
        if(md5(array_get($data,'sid').env('INSIGHT_USER_SIGN')) != $data['sign'] && $data['utype']=='nor') {
            return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1002,'msg'=>'签名错误！']));
        }
        # ------用户绑定Fd流程
        if(array_get($data,'action') == 'bind') {
            \Redis::hMset('WebSocketUserFdId:'.$frame->fd,['uid'=>$uid,'utype'=>$data['utype']]);
            \Redis::hMset('WebSocketUserIdFd:'.$data['utype'].':'.$uid,['fd'=>$frame->fd,'utype'=>$data['utype']]);
            # ------医生的绑定操作 start
            if($data['utype']=='doc') {
                \Redis::sAdd('WebSocketOnlineDoctorIds',$uid);
                $doctor_online = self::getOnlineDoctors();
                \Log::info(json_encode($doctor_online));
                foreach ($server->connections as $fd) {
                    # 对在线的普通用户推送医生列表
                    $user = \Redis::hGetAll('WebSocketUserFdId:'.$fd);
                    \Log::info($fd);
                    \Log::info($user);
                    if ($server->isEstablished($fd) && !empty($user) && $user['utype']=='nor') {
                        return $server->push($fd, json_encode(['action'=>'doctors_list','code'=>0,'msg'=>'','data'=>$doctor_online]));
                    }
                }
            }
            # ------医生的绑定操作 end
            return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>0,'msg'=>'绑定成功']));
        }
        # ------用户发送视频咨询流程  接收数据协议：{sid:1,action:'video_call',utype:'nor',sign:'yt8hr32gr2',data:{trade_no:20207431014,doctor_id:2,room:quryqwe9832}}
        if(array_get($data,'action') == 'video_call') {
            if( $data['utype']!='nor' ||
                empty($data['data']['trade_no']) ||
                empty($data['data']['doctor_id']) ||
                empty($data['data']['room'])
            )
            {
                return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1001,'msg'=>'请求参数有误']));
            }
            # 查询对应的订单
            $order_info = OrderDiagnoseService::findByTradeNo($data['data']['trade_no']);
            if( empty($order_info)||
                $order_info->user_id != $uid ||
                $order_info->is_used != 0 ||
                $order_info->status != 1 ||
                $order_info->diagnose_type != 1
            ) {
                return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1003,'msg'=>'订单错误！']));
            }
            # 查询医生信息
            $doctor_info = DoctorService::findById($data['data']['doctor_id']);
            $admin_user_id = $doctor_info->adminUsers->id;
            $doctor_online_info = \Redis::hGetAll('WebSocketUserIdFd:doc:'.$admin_user_id);
            if(empty($doctor_online_info)) {
                return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1004,'msg'=>'医生未在线，请稍后再试']));
            }
            # 接通视频需要的参数和签名
            $doctor_fd = $doctor_online_info['fd'];
            $timestamp = time();
            $private_key = env('INSIGHT_USER_SIGN');
            $trade_no = $data['data']['trade_no'];
            $room = $data['data']['room'];
            $duration = $order_info->diagnose->duration;
            $doctor_admin_sid = \Hashids::encode($admin_user_id);
            # 用户信息
            $user_info = UserService::getById($uid);
            $nickname = $user_info->nickname;
            $sign = md5($room.$trade_no.$duration.$timestamp.$private_key.$sid.$doctor_admin_sid);
            # 存redis
            $video_call_data = array(
                'user_id'=>$uid,
                'room_id'=>$room,
                'trade_no'=>$trade_no,
                'doctor_id'=>$data['data']['doctor_id'],
                //'start_time'=>date('Y-m-d H:i:s'),
                'is_connected'=>0,
                'closed_by_peer'=>0
            );
            //\Redis::hMset('video_call_data:'.$uid.':'.$trade_no.':'.$room.':',$video_call_data);

            # 发送视频请求给后台医生
            VideoCallService::add($video_call_data);
            $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>0,'msg'=>'请求发送成功']));
            return $server->push($doctor_fd,json_encode(['action'=>'receive_video_call','code'=>0,'data'=>['redirect'=>urlencode('room='.$room.'&trade_no='.$trade_no.'&timestamp='.$timestamp.'&duration='.$duration.'&sign='.$sign.'&sid='.$sid.'&admin_sid='.$doctor_admin_sid),'nickname'=>$nickname]]));
        }
        # -----发送视频请求 end
        # ----client 关闭视频通话 通知
        if(array_get($data,'action') == 'close_video') {
            if( $data['utype']!='nor' ||
                empty($data['data']['trade_no']) ||
                empty($data['data']['doctor_id']) ||
                empty($data['data']['room'])
            ){
                return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1001,'msg'=>'请求参数有误']));
            }
            else{
                $trade_no = $data['data']['trade_no'];
                $room = $data['data']['room'];
                /*$video_call_info = \Redis::hGetAll('video_call_data:'.$uid.':'.$trade_no.':'.$room.':');
                if(empty($video_call_info)) {
                    \Redis::hMset('video_call_data:'.$uid.':'.$trade_no.':'.$room.':',['user_id'=>$uid, 'room_id'=>$room, 'trade_no'=>$trade_no, 'doctor_id'=>$data['data']['doctor_id'], 'duration'=>0, 'is_connected'=>0, 'closed_by_peer'=>1]);
                }else{*/
               // $is_connected = \Redis::hGet('video_call_data:'.$uid.':'.$trade_no.':'.$room.':','is_connected');
               // $video_call_info = \Redis::hGetAll('video_call_data:'.$uid.':'.$trade_no.':'.$room.':');
               // if(is_null($video_call_info)) {
                $video_call_info = VideoCall::where('trade_no',$trade_no)->where('user_id',$uid)->where('room_id',$room)->first();
                    //$is_connected = isset($video_call_info->is_connected)?$video_call_info->is_connected:0;//VideoCall::where('trade_no',$trade_no)->where('user_id',$uid)->where('room_id',$room)->value('is_connected');
                    //$end_time = $video_call_info->start_time;
                //}
                $is_connected = isset($video_call_info->is_connected)?$video_call_info->is_connected:0;//VideoCall::where('trade_no',$trade_no)->where('user_id',$uid)->where('room_id',$room)->value('is_connected');
                $start_time = $video_call_info->start_time;
                $end_time = date('Y-m-d H:i:s');
                $duration = ceil((strtotime($end_time)-strtotime($start_time))/60);
                if($is_connected==1) {
                    //\Redis::hMset('video_call_data:'.$uid.':'.$trade_no.':'.$room.':',['closed_by_peer'=>1,'end_time'=>$end_time,'duration'=>$duration]);
                    //VideoCall::where('trade_no',$trade_no)->where('user_id',$uid)->where('room_id',$room)->update(['closed_by_peer'=>1,'end_time'=>$end_time,'duration'=>$duration]);
                    $video_call_info->closed_by_peer=1;
                    $video_call_info->end_time=$end_time;
                    $video_call_info->duration=$duration;
                    $video_call_info->save();
                    /*$order_diagnose = OrderDiagnoseService::findByTradeNo($data['data']['trade_no']);
                    $order_diagnose->is_used = 1;
                    $order_diagnose->save();

                    $diagnose = GraphicService::getById($order_diagnose->diagnose_id);
                    $diagnose->doctor_id=$data['data']['doctor_id'];
                    $diagnose->duration = $duration;
                    $diagnose->save();*/ //完成通话不自动完成
                }else{
                    //\Redis::hset('video_call_data:'.$uid.':'.$trade_no.':'.$room.':','closed_by_peer',1);
                    //VideoCall::where('trade_no',$trade_no)->where('user_id',$uid)->where('room_id',$room)->update(['closed_by_peer'=>1]);
                    $video_call_info->closed_by_peer =1;
                    $video_call_info->save();

                }
                //return $server;
                $doctor_info = DoctorService::findById($video_call_info->doctor_id);
                $admin_user_info = $doctor_info->adminUsers;
                $doctor_online_info = \Redis::hGetAll('WebSocketUserIdFd:doc:'.$admin_user_info->id);
                $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>0,'msg'=>'关闭成功']));
                if($server->isEstablished($doctor_online_info['fd'])) {
                    return $server->push($doctor_online_info['fd'],json_encode(['action'=>'close_by_peer_notify','data'=>['video_call'=>$video_call_info]]));
                }
            }
        }

        if(array_get($data,'action') == 'video_connected') {
            if( $data['utype']!='doc' ||
                empty($data['data']['trade_no']) ||
                empty($data['data']['room'])
            ){
                return $server->push($frame->fd, json_encode(['action'=>'tips','tips_type'=>$data['action'],'code'=>1001,'msg'=>'请求参数有误']));
            }else{
                $doctor_info = DoctorService::findByAdminUserId($uid);
                $video_call = VideoCall::where('trade_no',$data['data']['trade_no'])->where('doctor_id',$doctor_info->id)->where('room_id',$data['data']['room'])->first();
                $video_call->is_connected = 1;
                $video_call->save();
                //\Redis::hset('video_call_data:'.$video_call->user_id.':'.$data['data']['trade_no'].':'.$data['data']['room'].':','is_connected',1);
            }
        }
    }
    //当客户端和服务端断开连接时要执行的方法
    public function onClose($server,$fd) {
        # 删除redis哈希
        $user_info = \Redis::hGetAll('WebSocketUserFdId:'.$fd);
        if($user_info) {
            \Redis::Del('WebSocketUserFdId:'.$fd,'WebSocketUserIdFd:'.$user_info['utype'].':'.$user_info['uid']);
            if($user_info['utype']=='doc') {
                \Redis::sRem('WebSocketOnlineDoctorIds',$user_info['uid']);
                $doctor_online = self::getOnlineDoctors();
                \Log::info(json_encode($doctor_online));
                foreach ($server->connections as $fd) {
                    # 对在线的普通用户推送医生列表
                    $user = \Redis::hGetAll('WebSocketUserFdId:'.$fd);
                    \Log::info($fd);
                    \Log::info($user);
                    if ($server->isEstablished($fd) && !empty($user) && $user['utype']=='nor') {
                        $server->push($fd, json_encode(['action'=>'doctors_list','code'=>0,'msg'=>'','data'=>$doctor_online]));
                    }
                }
            }
        }
    }

    //启动webSocket方法
    public static function start(){
        self::$server->start();
    }

    /**
     * {action:'bind',sid:'ND',utype:'doc'}
     * @param $data
     * @return bool
     */
    private static function validate_req_data($data) {
        if(!is_array($data)) {
            return false;
        }
        if(!array_key_exists('action',$data)||
           !array_key_exists('sid',$data)||
           !array_key_exists('utype',$data)||
           !array_key_exists('sign',$data)
        ) {
            return false;
        }
        if(!in_array($data['action'],['bind','video_call','close_video']) ||
            !in_array($data['utype'],['doc','nor']) //doc 医生 nor 普通用户
        ) {
            return false;
        }
        return true;
    }

    public static function getOnlineDoctors() {

        $doctor_ids = \Redis::sMembers('WebSocketOnlineDoctorIds');
        $doctors_list = DoctorService::getByAdminIds($doctor_ids);
        \Log::info(json_encode($doctors_list));
        $doctor_online = array();
        $now = time();
        foreach ($doctors_list as $key=>$doctor) {
            \Log::info('start_time:'.strtotime(date('Y-m-d '.$doctor->consultingTime->start_time.':s')));
            \Log::info('end_time:'.strtotime(date('Y-m-d '.$doctor->consultingTime->end_time.':s')));
            # 把所有在线且在工作时间内的医生存进数组
            if($doctor->is_consulting && $doctor->consultingTime
                && $doctor->consultingTime->status==1 &&
                ($doctor->consultingTime->weeks_type == date('w') || $doctor->consultingTime->weeks_type == -1) &&
                strtotime(date('Y-m-d '.$doctor->consultingTime->start_time.':s')) <= $now &&
                strtotime(date('Y-m-d '.$doctor->consultingTime->end_time.':s')) > $now
            ) {
                $doctor->photo = env('APP_DOMAIN').$doctor->photo;
                array_push($doctor_online,$doctor);
            }
        }
        return $doctor_online;
    }
}

